Terraformで、同じ構成を複数プロビジョニングしたい: Terragruntでbackendを動的設定編
先日 HashiTalks Japanで「シングルテナント構成のSaaSのIaCにTerraform Workspacesを導入してみた」というビデオ登壇をしました。その中で時間の都合でご紹介できなかった、「Workspacesを使う以外の、同じ構成(リソースセット)を複数個プロビジョニングする方法案」を、複数回に分けてご紹介していきます。
関連エントリ
- #HashiTalks Japanで「シングルテナント構成のSaaSのIaCにTerraform Workspacesを導入してみた」という発表をしました
- Terraformで、同じ構成を複数プロビジョニングしたい: Module編
- Terraformで、同じ構成を複数プロビジョニングしたい: ディレクトリ分割編
- Terraformで、同じ構成を複数プロビジョニングしたい: Terragruntでrun-all編
- Terraformで、同じ構成を複数プロビジョニングしたい: backend-configオプション編
- Terraformで、同じ構成を複数プロビジョニングしたい: Terragruntでbackendを動的設定編 ?イマココ
詳細
まずはTerragruntをインストールします。
terraform.backend
ブロックは定義しません。
terraform { required_version = "= 1.2.7" # backend "s3" { # bucket = "hogehoge-terraform" # key = "dev.tfstate" # region = "us-east-1" # } required_providers { aws = { version = "~> 4.26.0" } } }
同じディレクトリ上にterragrunt.hcl
を作成し、以下のように書きます。
locals { prefix = get_env("PREFIX") } generate "backend" { path = "backend.tf" if_exists = "overwrite" contents = <<EOT terraform { backend "s3" { bucket = "hogehoge-terraform" key = "${local.prefix}_main.tfstate" region = "ap-northeast-1" encrypt = true } } EOT } generate "tfvars" { path = "terraform.tfvars" if_exists = "overwrite" contents = <<EOT prefix = "${local.prefix}" EOT }
見ていただくと大体何をしているファイルかおわかりいただけるのではないかと思いますが、説明します。
- まず、
prefix
というTerragruntのローカル変数を定義しています。get_env()
関数はその名の通り環境変数を取得する関数です。 - 上記
prefix
ローカル変数を使って、generate
ブロックで2つのファイルを動的に作成しています。generate
ブロックは、contents
以下の内容のファイルをpath
のファイル名で作成するものです。 - ひとつはバックエンドの設定を書いたファイルです。
- もう一つは変数値のファイルです。
terraform.tfvars
という名前にしてTerraformコマンド実行ディレクトリと同じディレクトリに置いておくと、自動で読み込んでくれます。
次に、上記で参照している環境変数を設定します。
% export PREFIX=hoge
また、プロビジョニング対象となるリソースはS3バケットひとつだけとします。前述のterraform.tfvars
内で記述しているprefix
variableを定義して参照しています。
variable "prefix" { type = string } resource "aws_s3_bucket" "main" { bucket_prefix = var.prefix }
この状態でterragrunt plan
します。(Terragruntの場合initは必要があれば内部的に自動でやってくれます)
すると、backendの設定が書かれたファイルと変数値のファイルが作成されます。さらにその設定を踏まえてplan結果が出力されます。
# Generated by Terragrunt. Sig: nIlQXj57tbuaRZEa terraform { backend "s3" { bucket = "hogehoge-terraform" key = "hoge_main.tfstate" region = "ap-northeast-1" encrypt = true } }
# Generated by Terragrunt. Sig: nIlQXj57tbuaRZEa prefix = "hoge"
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # aws_s3_bucket.main will be created + resource "aws_s3_bucket" "main" { + acceleration_status = (known after apply) + acl = (known after apply) + arn = (known after apply) + bucket = (known after apply) + bucket_domain_name = (known after apply) + bucket_prefix = "hoge" + bucket_regional_domain_name = (known after apply) + force_destroy = false + hosted_zone_id = (known after apply) + id = (known after apply) + object_lock_enabled = (known after apply) + policy = (known after apply) + region = (known after apply) + request_payer = (known after apply) + tags_all = (known after apply) + website_domain = (known after apply) + website_endpoint = (known after apply) + cors_rule { + allowed_headers = (known after apply) + allowed_methods = (known after apply) + allowed_origins = (known after apply) + expose_headers = (known after apply) + max_age_seconds = (known after apply) } + grant { + id = (known after apply) + permissions = (known after apply) + type = (known after apply) + uri = (known after apply) } + lifecycle_rule { + abort_incomplete_multipart_upload_days = (known after apply) + enabled = (known after apply) + id = (known after apply) + prefix = (known after apply) + tags = (known after apply) + expiration { + date = (known after apply) + days = (known after apply) + expired_object_delete_marker = (known after apply) } + noncurrent_version_expiration { + days = (known after apply) } + noncurrent_version_transition { + days = (known after apply) + storage_class = (known after apply) } + transition { + date = (known after apply) + days = (known after apply) + storage_class = (known after apply) } } + logging { + target_bucket = (known after apply) + target_prefix = (known after apply) } + object_lock_configuration { + object_lock_enabled = (known after apply) + rule { + default_retention { + days = (known after apply) + mode = (known after apply) + years = (known after apply) } } } + replication_configuration { + role = (known after apply) + rules { + delete_marker_replication_status = (known after apply) + id = (known after apply) + prefix = (known after apply) + priority = (known after apply) + status = (known after apply) + destination { + account_id = (known after apply) + bucket = (known after apply) + replica_kms_key_id = (known after apply) + storage_class = (known after apply) + access_control_translation { + owner = (known after apply) } + metrics { + minutes = (known after apply) + status = (known after apply) } + replication_time { + minutes = (known after apply) + status = (known after apply) } } + filter { + prefix = (known after apply) + tags = (known after apply) } + source_selection_criteria { + sse_kms_encrypted_objects { + enabled = (known after apply) } } } } + server_side_encryption_configuration { + rule { + bucket_key_enabled = (known after apply) + apply_server_side_encryption_by_default { + kms_master_key_id = (known after apply) + sse_algorithm = (known after apply) } } } + versioning { + enabled = (known after apply) + mfa_delete = (known after apply) } + website { + error_document = (known after apply) + index_document = (known after apply) + redirect_all_requests_to = (known after apply) + routing_rules = (known after apply) } } Plan: 1 to add, 0 to change, 0 to destroy.
terragrunt apply
すればS3バケットが作成されます。
※ backend.tf
terraform.tfvars
はGit管理対象外にしておくのが良いでしょう。
別プロビジョニング先の設定
環境変数を更新します。
% export PREFIX=fuga
バックエンドの設定を変えたので、今回はinitが必要です。(CI環境のような毎回捨てる環境なら不要です)
% terragrunt init -reconfigure
もしくはワーキングディレクトリの.terraform
ディレクトリを削除してください。
applyします。
% terragrunt apply
先程作ったバケット(hoge〜)は残ったまま、新しいバケット(fuga〜)もできていますね。 stateファイルも別々に作成されています。
こんな感じで、環境変数値を切り替えることで同じ構成を複数プロビジョニングすることが可能になります。
動的生成部分もTerragrunt内でやっちゃう
前述の方法だと、プロビジョニング先を切り替える際に、事前に環境変数の更新が必要ですね。できればこの手間も無くしたいです。
run_cmd関数を使えば Terragrunt内でシェルスクリプトを実行することも可能です。これを利用してみます。
locals { - prefix = get_env("PREFIX") + prefix = run_cmd("./bash/get_prefix.sh") }
#!/bin/bash echo "ppp"
面倒だったので 今回は単純に固定値を返すスクリプトにしていますが、このシェルスクリプトを作り込めばTerragruntだけで複数環境の切り替えができますね。
この構成の良い点
展開先の情報がコードに出ない、Gitに残らない
Module案などと異なり展開先の情報がコードに現れないので、その情報を隠蔽化したい際には適しています。例えばシングルテナント構成のSaaSを作る際などには向いているでしょう。
backendの設定が自在
Terraform Workspacesに対する優位点です。
Workspacesを使う場合、Stateファイルの格納場所は固定です。S3をbackendに使う場合、Stateファイルはすべて同じバケット内のenv:/(workspace名)/(key名)
に格納されます。
例えばWorkspace案でクロスアカウントでプロビジョニングする場合、Stateファイル格納先は1アカウントに集中することになります。展開先のアカウントに別々に格納することはできません。
対してこの構成だともっと自由にStateファイル格納場所を選ぶことができます。オブジェクトパスはもちろん、バケットを別々にすることも、違うリージョンにすることも、別アカウントにすることもできますね。
Terragruntだけで完結できる
backend-configオプション案だとBackendの設定をTerraform外で行なう都合上、その外出し先との連携を徹底する(例えばシェルスクリプト化して 外部処理→Terraformコマンドを一気通貫で行なうようにするなど)必要があります。が、前述のとおり Terragruntだとその外出しの部分もTerragrunt内のシェルスクリプトとして内部化できるので、普段扱うコマンドはTerragruntだけで済みます。
Terraformコマンドも使える
TerragruntでやっているのはTerraformの設定ファイルを2つ作ることだけなので、そのファイルが作成済の場合、Terragruntコマンドの代わりにTerraformコマンドを使っても同じ結果になります。つまりプロビジョニング先を変えたいとき以外は慣れ親しんだTerraformコマンドを使うことができます。
この構成のイマイチな点
Terragrunt学習コスト
当たり前ですがTerragruntについて学ぶ必要があります。この構成をそのまま使うのであれば問題ないと思います。が、ちょっと踏み込んだことを足そうと思ったときに、「できそうだけどできないなー」みたいなことが沢山起きる、かもしれません。私は今回この構成を検証するにあたり最初Hooksを使えば良さそうと考えて試したのですが断念しました。Terragruntの処理の流れ、順序を理解するのが使いこなす近道のような気がします。(道半ば)
展開先ごとの差分の実現方法が限られる
展開先の情報が全くコードに出てこないので、もし展開先毎にプロビジョニングしたいリソースが異なるような要件があった場合、それを実現する方法は変数ファイル(.tfvars
)の値を変えるくらいしかありません。
ユースケース
シングルテナント構成のSaaSのような、展開先毎のプロビジョニング内容にほぼほぼ差分がない、かつ展開先が多い場合に向いていると思います。 Workspaces案のほうがシンプルですが、それでは対応できない要件がある場合に検討するのが良いでしょう。